/*
 * ALU class handles the Arithmetic and Logical operations.  ALU is an acronym for
 * Arithmetic Logic Unit.  Handles scalar addition, subtraction, multiplication,
 * division, logical and, logical or and logical not operations.
 * 
 * Component registers are Signed Arithmetic Register, First Operand Register, 
 * Second Operand Register and Operational Result Register
 * 
 * Operations may use the SRU (Shift/Rotate Unit) to ensure that resultant values 
 * maintain signs and maintain compatibility with the system.
 * 
 * Signed and unsigned operations are available; however, as currently defined, the 
 * ISA makes no provisions for unsigned operations. 
 *
 * Note that condition codes are cleared upon invocation of an ALU operation and
 * the current condition state remains valid for several operations following an 
 * ALU instruction as long as no other instruction is called that may also result 
 * in a condition code being set such as SRU operations.
 * 
 * In addition and subtraction operations, the low-word of the Result Register will
 * contain the 16-bit result and the high-word will contain any overflow.  For signed
 * addition and subtraction, because the sign bit is attached to the high-word, the
 * low-word is shifted and the sign bit is inserted into the low-word to minimize the 
 * need for program instructions to handle this standard conversion for all add and
 * subtract operations.  In such cases where a bit is shifted out of the low-word, the
 * OVERFLOW condition will be set accordingly.
 * 
 * In multiplication operations, the resultant 32-bit value is not shifted as is done in
 * addition and subtraction operations; therefore, it is left to the programmer to 
 * check the high-word for potential overflow as well as to shift the sign bit into the
 * low-word and check for any bit ejected from the low-word during the shift.
 * 
 * In division operations, the result register is partitioned along the word boundary such
 * that the low-word contains the quotient and the high-word contains the remainder.
 * 
 * In logical operations, the result register low-word will contain the the result as there
 * is no potential for boundary violations.  
 * */

package cpu.components;

import cpu.datastores.*;

public class ALU {
	// A register to hold whether or not this is signed arithmetic.
	public Register1 ALS;
	
	// First Arithmetic/Logical Operand Register
	public Register16 ALO0;
	
	// Second Arithmetic/Logical Operand Register
	public Register16 ALO1;
	
	// Arithmetic/Logical Result Buffer Register
	public Register32 ALR;

	// Reference to the cpu to access condition codes.
	private cpu.components.CPU cpu;
	
	public ALU( cpu.components.CPU cpu ) {
		this.cpu = cpu;
		
		ALS = new Register1( );
		ALO0 = new Register16( );
		ALO1 = new Register16( );
		/*
		ALR0 = new Register16( );
		ALR1 = new Register16( );
		*/
		ALR = new Register32( );
	}
	
	// Arithmetic Integer Add operation
	// ALR = ALO0 + ALO1
	public void add( ) {
		cpu.controlunit.cc( ControlUnit.UNDERFLOW, false);
		cpu.controlunit.cc( ControlUnit.OVERFLOW, false);
		cpu.controlunit.cc( ControlUnit.DIVBYZERO, false);
		cpu.controlunit.cc( ControlUnit.EQUALORNOT, false);

		if( ALS.bit( 0 ).value ) {
			// signed
			
			int accumulator = ALO0.toSignedInteger( ) + ALO1.toSignedInteger( );
			
			boolean negative = false;
			if( accumulator < 0 )
				negative = true;

			ALR.fromLongInteger( new cpu.datatypes.LongInteger( accumulator ) );
			
			if( accumulator < -32767 || accumulator > 32767 )
				cpu.controlunit.cc( ControlUnit.OVERFLOW, true );
				
			if( cpu.controlunit.cc( ControlUnit.OVERFLOW ).value == false ) {
				// shift sign to low word
				cpu.sru.SRCT.fromJavaInt( 0 );
				cpu.sru.SRD.bit( 0 ).value = SRU.LEFT;
				cpu.sru.SRT.bit( 0 ).value = SRU.LOGICAL;
				cpu.sru.SRB.fromRegister( ALR.wordLow );
				cpu.sru.shift( );
				ALR.wordLow.fromRegister( cpu.sru.SRB );
				ALR.wordLow.bit( 0 ).value = negative;
				// note overflow may have been set by SRU
			}
		} else {
			int accumulator = ALO0.toJavaInt( ) + ALO1.toJavaInt( );
			// unsigned
			
			ALR.fromJavaInt( accumulator );
			
			if( ALR.wordHigh.toJavaInt( ) > 0 ) {
				cpu.controlunit.cc( ControlUnit.OVERFLOW, true );
			}
		}
	}
	
	public void subtract( ) {
		cpu.controlunit.cc( ControlUnit.UNDERFLOW, false);
		cpu.controlunit.cc( ControlUnit.OVERFLOW, false);
		cpu.controlunit.cc( ControlUnit.DIVBYZERO, false);
		cpu.controlunit.cc( ControlUnit.EQUALORNOT, false);

		if( ALS.bit( 0 ).value ) {
			// signed
			
			int accumulator = ALO0.toSignedInteger( ) - ALO1.toSignedInteger( );

			boolean negative = false;
			if( accumulator < 0 )
				negative = true;

			ALR.fromLongInteger( new cpu.datatypes.LongInteger( accumulator ) );
			
			if( accumulator < -32767 || accumulator > 32767 )
				cpu.controlunit.cc( ControlUnit.OVERFLOW, true );

			if( cpu.controlunit.cc( ControlUnit.OVERFLOW ).value == false ) {
				// shift sign to low word
				cpu.sru.SRCT.fromJavaInt( 0 );
				cpu.sru.SRD.bit( 0 ).value = SRU.LEFT;
				cpu.sru.SRT.bit( 0 ).value = SRU.LOGICAL;
				cpu.sru.SRB.fromRegister( ALR.wordLow );
				cpu.sru.shift( );
				ALR.wordLow.fromRegister( cpu.sru.SRB );
				ALR.wordLow.bit( 0 ).value = negative;
				// note overflow may have been set by SRU
			}
		} else {
			// unsigned
			
			int accumulator = ALO0.toJavaInt( ) - ALO1.toJavaInt( );
			
			ALR.fromJavaInt( accumulator );

			if( ALR.wordHigh.toJavaInt( ) > 0 ) {
				cpu.controlunit.cc( ControlUnit.OVERFLOW, true );
			}
		}
	}
	
	public void multiply( ) {
		cpu.controlunit.cc( ControlUnit.UNDERFLOW, false);
		cpu.controlunit.cc( ControlUnit.OVERFLOW, false);
		cpu.controlunit.cc( ControlUnit.DIVBYZERO, false);
		cpu.controlunit.cc( ControlUnit.EQUALORNOT, false);

		if( ALS.bit( 0 ).value ) {
			// signed
			int accumulator = ALO0.toSignedInteger( ) * ALO1.toSignedInteger( );

			boolean overflow = ALR.fromLongInteger( new cpu.datatypes.LongInteger( accumulator ) );
			
			if( overflow )
				cpu.controlunit.cc( ControlUnit.OVERFLOW, true );
			
		} else {
			// unsigned
			int accumulator = ALO0.toJavaInt( ) * ALO1.toJavaInt( );
			
			boolean overflow = ALR.fromJavaInt( accumulator );

			if( overflow )
				cpu.controlunit.cc( ControlUnit.OVERFLOW, true );
		}
	}
	
	// Arithmetic Integer Divide operation
	public void divide( ) {
		cpu.controlunit.cc( ControlUnit.UNDERFLOW, false);
		cpu.controlunit.cc( ControlUnit.OVERFLOW, false);
		cpu.controlunit.cc( ControlUnit.DIVBYZERO, false);
		cpu.controlunit.cc( ControlUnit.EQUALORNOT, false);

		if( ALS.bit( 0 ).value ) {
			// signed
			
			if( ALO1.toSignedInteger( ) == 0 ) {
				cpu.controlunit.cc( ControlUnit.DIVBYZERO, true );
				return;
			}			
			int dividend = (int)ALO0.toSignedInteger( );
			int divisor = (int)ALO1.toSignedInteger( );
			int quotient, remainder;
			
			quotient = dividend / divisor;
			if( quotient * divisor == dividend )
				remainder = 0;
			else {
				if( dividend < 0 && divisor < 0 )
					remainder = -dividend % -divisor;
				else if( dividend < 0 || divisor < 0 )
					// strange case.  dependent upon the way java does modulo
					// may have to revisit
					remainder = dividend % divisor;
				else
					remainder = dividend % divisor;
			}
			// ?underflow? 
			
			ALR.wordLow.fromSignedInteger( quotient );
			ALR.wordHigh.fromSignedInteger( remainder );
		} else {
			// unsigned
			
			if( ALO1.toJavaInt( ) == 0 ) {
				cpu.controlunit.cc( ControlUnit.DIVBYZERO, true );
				return;
			}
			
			int dividend = (int)ALO0.toJavaInt( );
			int divisor = (int)ALO1.toJavaInt( );
			int quotient, remainder;
			
			quotient = dividend / divisor;
			if( quotient * divisor == dividend )
				remainder = 0;
			else
				remainder = dividend % divisor;

			// ?underflow? 
			
			ALR.wordLow.fromJavaInt( quotient );
			ALR.wordHigh.fromJavaInt( remainder );
		}
	}
	
	// test equivalence of two words 
	public void equivalent( ) {
		cpu.controlunit.cc( ControlUnit.UNDERFLOW, false);
		cpu.controlunit.cc( ControlUnit.OVERFLOW, false);
		cpu.controlunit.cc( ControlUnit.DIVBYZERO, false);
		cpu.controlunit.cc( ControlUnit.EQUALORNOT, false);

		for( int i = 0; i < 16; i++ ) {
			if( ALO0.bit(  i  ).value != ALO1.bit(  i  ).value ) {
				cpu.controlunit.cc( ControlUnit.EQUALORNOT, false );
				return;
			}
		}
		cpu.controlunit.cc( ControlUnit.EQUALORNOT, true );
	}
	
	// Logical And operation
	// ALR0 = ALO0 and ALO1
	public void and( ) {
		/*
		cpu.controlunit.cc( ControlUnit.UNDERFLOW, false);
		cpu.controlunit.cc( ControlUnit.OVERFLOW, false);
		cpu.controlunit.cc( ControlUnit.DIVBYZERO, false);
		cpu.controlunit.cc( ControlUnit.EQUALORNOT, false);
		 */
		
		for( int i = 0; i < 16; i++ ) {
			if( ALO0.bit( i ).value && ALO1.bit( i ).value )
				ALR.wordLow.bit( i ).value = true;
			else
				ALR.wordLow.bit( i ).value = false;
		}
		for( int i = 0; i < 16; i++ ) {
			ALR.wordHigh.bit( i ).value = false;
		}
	}
	
	// Logical Or operation
	// ALR0 = ALO0 or ALO1
	public void or( ) {
		/*
		cpu.controlunit.cc( ControlUnit.UNDERFLOW, false);
		cpu.controlunit.cc( ControlUnit.OVERFLOW, false);
		cpu.controlunit.cc( ControlUnit.DIVBYZERO, false);
		cpu.controlunit.cc( ControlUnit.EQUALORNOT, false);
		 */
		
		for( int i = 0; i < 16; i++ ) {
			if( ALO0.bit( i ).value || ALO1.bit( i ).value )
				ALR.wordLow.bit( i ).value = true;
			else
				ALR.wordLow.bit( i ).value = false;
		}
		for( int i = 0; i < 16; i++ ) {
			ALR.wordHigh.bit( i ).value = false;
		}
	}
	
	// Logical Not operation
	// ALR0 = not( ALO0 )
	public void not( ) {
		/*
		cpu.controlunit.cc( ControlUnit.UNDERFLOW, false);
		cpu.controlunit.cc( ControlUnit.OVERFLOW, false);
		cpu.controlunit.cc( ControlUnit.DIVBYZERO, false);
		cpu.controlunit.cc( ControlUnit.EQUALORNOT, false);
		 */
		
		for( int i = 0; i < 16; i++ ) {
			if( ALO0.bit( i ).value )
				ALR.wordLow.bit( i ).value = false;
			else
				ALR.wordLow.bit( i ).value = true;
		}
		for( int i = 0; i < 16; i++ ) {
			ALR.wordHigh.bit( i ).value = false;
		}
	}
}
